/**
 * 无向图
 *
 * 广度优先搜索：初始化每个点的标记为未检查，选一个起始点入队列，贴上待检查标记，
 * 然后该点出队列，寻找所有标记为未检查的相邻点并将其入栈（同时修改其标记为待检查），
 * 对于已经出队列的点，标记为已检查。终止条件：队列为空
 *
 * 深度优先：采用迭代的方式，类似于树的遍历，但是要在遍历过程中检查当前点是否已经并遍历过
 */

function Graph(){
    this.vertices = [];//图的顶点
    this.edgeList = new Map();//采用邻接表保存边，使用的是JavaScript的Map数据结构
}
Graph.prototype = {
    //添加顶点
    addVertex: function(value){
        this.edgeList.set(value, []);
        return this.vertices.push(value);
    },
    //添加边，两个方向都添加
    addEdge: function(value1, value2){
        if(!this.edgeList.get(value1))return false;
        if(!this.edgeList.get(value2))return false;
        this.edgeList.get(value1).push(value2);
        this.edgeList.get(value2).push(value1);
        return true;
    },
    //打印邻接表到控制台
    print: function(){
        var str = "";
        for(let i = this.vertices.length-1;i>=0;i--){
            str += this.vertices[i]+" : ";
            str += this.edgeList.get(this.vertices[i]).join(",");
            str += "\n";
        }
        console.log(str);
        return str;
    },
    //广度优先搜索
    bfs: function(callback,start){
        //distances、predecessors用于统计最短距离
        var distances = [];//距离
        var predecessors = [];//前溯点

        start = start || this.vertices[0];
        var queue = [];
        queue.push(start);//注意，push会改变原数组，但是其返回值是新数组的长度
        var edgeList = this.edgeList;
        var isTrav = new Map();
        this.vertices.forEach(function(v){
            isTrav.set(v,0);
            distances[v] = 0;
            predecessors[v] = null;
        });

        isTrav.set(start, 1);
        while(queue.length>0){
            var u = queue.shift();
            var neighbors = edgeList.get(u);
            for(let i=0;i<neighbors.length;i++){
                var w = neighbors[i];
                if(isTrav.get(w) === 0){
                    isTrav.set(w, 1);
                    queue.push(w);
                    distances[w] = distances[u] + 1;
                    predecessors[w] = u;
                }
            }
            isTrav.set(u, 2);
            callback(u);
        }

        return {
            distances:distances,
            predecessors:predecessors
        };
    },
    //深度优先搜索
    dfs: function(callback, start){
        start = start || this.vertices[0];
        var isTrav = new Map();
        this.vertices.forEach(function(v){
            isTrav.set(v,0);
        });
        this.dfsVisit(start, isTrav, callback);
    },
    dfsVisit: function(n, isTrav, callback){
        isTrav.set(n, 1);
        callback(n);
        var neighbors = this.edgeList.get(n);
        for(let i=0;i<neighbors.length;i++){
            var w = neighbors[i];
            if(isTrav.get(w) == 0){
                this.dfsVisit(w, isTrav, callback);
            }
        }
        isTrav.set(n, 2);
    }
};


var graph = new Graph();
var myVertices = ['A','B','C','D','E','F'];
myVertices.forEach(function(v){
    graph.addVertex(v);
});
graph.addEdge('A','B');
graph.addEdge('A','C');
graph.addEdge('A','D');
graph.addEdge('C','D');
graph.addEdge('C','E');
graph.addEdge('D','E');
graph.addEdge('D','H');
graph.addEdge('B','E');
graph.addEdge('B','F');

console.log("邻接表：");
graph.print();
console.log("广度优先：");
var d = graph.bfs(function(v){console.log(v);});
console.log(d);
console.log("深度优先：");
graph.dfs(function(v){console.log(v);});